January 90 - BRAVING OFFSCREEN WORLDS
BRAVING OFFSCREEN WORLDS
GUILLERMO ORTIZ
No one disputes the benefits of using offscreen environments to prepare and keep the
contents of windows. The only problem is that creating such environments--from a
simple pixMap to a more complicated GDevice--can be rather difficult. 32-Bit
QuickDraw includes a set of calls that allows an application to create offscreen worlds
easily and effectively. This article describes those calls and provides details on how to
use them.
Until now, creating and maintaining offscreen devices or ports has been complicated
and confusing at best. As part of 32-Bit QuickDraw, Apple's engineering team has
included a set of calls that makes creating and maintaining offscreen devices and ports
a real breeze. Using the offscreen graphics environment, QuickDraw can maintain the
data alignment necessary to improve the performance of CopyBits when you use it to
display onscreen the contents of the offscreen buffer.
Also, applications using the offscreen world support from QuickDraw are more likely
to benefit from future enhancements to QuickDraw than programs doing their own
offscreen management. This can save you a lot of time down the road.
The offscreen world offers a few more benefits:
• The system takes care of all the messy details involved in creating the
offscreen GDevice. You don't need to bother asking "What flags should I set?
or "What is the refNum for an offscreen device?
• You can tailor the offscreen port according to your specifications rather
than being restricted to screenBits.bounds. Because the visRgn comes set to the
right dimensions, you won't need to change anything.
• The pixMap associated with the offscreen world comes back from the call
complete and ready to use. You won't have to lose sleep wondering if you set
the correct value for rowBytes or if your baseAddress pointer has less
memory than it should because the compiler didn't do the multiplication using
Longs.
Think of the GWorld structure as an extension to the CGrafPort structure, containing
the port information along with the device data and some extra state information. In
most cases, a GWorldPtr can be used interchangeably with a CGrafPtr, which makes
converting applications quite easy. At this point, however, Apple is keeping the
structure of an offscreen graphics world private to allow for further expansion.
For instance, in the following section of Developer Technical Support's sample
program FracApp, the new calls were implemented without changing the document's
data structure at all. This example illustrates the difference the new set of calls can
make when creating offscreen environments.
A LOOK AT THE NEW CALLS
Let's use a section of the sample program FracApp to illustrate the difference this new
set of calls can make when creating offscreen environments. The procedure
TFracAppDocument.BuildOffWorld creates a new device and its accompanying
structures for each document. Here is the original code, with comments shortened,
followed by the equivalent code using the new calls:
PROCEDURE TFracAppDocument.BuildOffWorld (sizeOfDoc: Rect);
VAR oldPerm: Boolean;
dummy: Boolean;
docW, docH: LongInt;
fi: FailInfo;
currDevice: GDHandle;
currPort: GrafPtr;
Erry: OSErr;
PROCEDURE DeathBuildOff (error: OSErr; message: LONGINT);
BEGIN
oldPerm := PermAllocation (oldPerm);
{ Set memory back to previous. }
SetGDevice (currDevice);
{ Set device back to main, just in case. }
SetPort (currPort);
END;
BEGIN
currDevice := GetGDevice; { save current for error handling. }
GetPort(currPort);
oldPerm := PermAllocation (TRUE);
CatchFailures(fi, DeathBuildOff); { any failures, must be }
{ cleaned up. }
{ Let's set up the size of the rectangle we are using for the }
{ document. }
docW := sizeOfDoc.right - sizeOfDoc.left;
docH := sizeOfDoc.bottom - sizeOfDoc.top;
{ Now try to set up the offscreen bitMap (color). }
fBigBuff := NewPtr (docW * docH);
FailMemError; { couldn't get it we die. }
{ OK, now we get wacko. We need to create our own gDevice, }
fDrawingDevice := NewGDevice (0, -1); { -1 means unphysical }
{ device. }
FailNIL (fDrawingDevice); { If we failed, error out. }
{ Now init all the fields in the gDevice Record, since it comes }
{ uninitialized. }
HLock ( Handle(fDrawingDevice) );
WITH fDrawingDevice^^ DO BEGIN
gdId := 0; { no ID for search & complement procs }
gdType := clutType; { color table type fer sure. }
DisposCTable (gdPMap^^.pmTable); { kill the stub that is }
{ there. }
gdPMap^^.pmTable := gOurColors; { make a copy of our }
{ global color table. }
Erry := HandToHand (Handle(gdPMap^^.pmTable));
FailOSErr (Erry); { if not possible, blow out. }
{ build a new iTable for this device }
MakeITable (gdPMap^^.pmTable, gdITable, 3);
FailOSErr (QDError);{ no memory, we can leave here. }
gdResPref := 3;{ preferred resolution in table. }
gdSearchProc := NIL; { no search proc. }
gdCompProc := NIL; { no complement proc. }
{ Set the gdFlags }
gdFlags := 2**0 + 2**10 + 2**14 + 2**15; { set each bit we }
{ need. }
{ Now set up the fields in the offscreen PixMap }
gdPMap^^.baseAddr := fBigBuff; { The base address is our }
{ buffer. }
gdPMap^^.bounds := sizeOfDoc; { bounding rectangle to our }
{ device. }
gdPMap^^.rowBytes := docW + $8000;
gdPMap^^.pixelSize := 8;
gdPMap^^.cmpCount := 1;
gdPMap^^.cmpSize := 8;
gdRect := sizeOfDoc; { the bounding rectangle for gDevice, }
{ too. }
END; { With fDrawingDevice }
HUnLock ( Handle(fDrawingDevice) );
SetGDevice (fDrawingDevice);
fDrawingPort := CGrafPtr( NewPtr (SizeOf (CGrafPort)) );
{ addr CPort record. }
FailNil (fDrawingPort); { didn’t get it, means we die. }
{ Now the world is created }
dummy := PermAllocation (FALSE);
OpenCPort (fDrawingPort); { make a new port offscreen. }
FailNoReserve; { Make reserve, die if we can’t }
{ QuickDraw is most obnoxious about making a port that is bigger
than the screen, so we need to modify the visRgn to make it as
big as our full page document }
RectRgn(fDrawingPort^.visRgn, sizeOfDoc);
fDrawingPort^.portRect := sizeOfDoc;
{ OK, we have a nice new color port that is offscreen. }
Success (fi);
oldPerm := PermAllocation (oldPerm);
{ Now we have the offscreen PixMap, we need to initialize it to
white. }
SetPort (GrafPtr(fDrawingPort));
EraseRect (sizeOfDoc); { clear the bits. }
SetGDevice (currDevice);
SetPort (currPort);
Now let's see what the equivalent code looks like when we use the calls provided by
32-Bit QuickDraw and its offscreen support:
PROCEDURE TFracAppDocument.BuildOffWorld(sizeOfDoc:RECT);
VAR oldPerm :Boolean;
fi :FailInfo;
currDev :GDHandle;
currPort :CGrafPtr;
erry :QDErr;
PROCEDURE DeathBuildOff (error: OSErr; message:LONGINT);
BEGIN
oldPerm := PermAllocation(oldPerm);
SetGWorld (currPort, currDev);
END;
BEGIN (*myBuildOffWorld*)
GetGWorld(currPort, currDev);
CatchFailures(fi, DeathDocument);
Erry := NewGWorld(fDrawingPort, 8, sizeOfDoc, gOurColors, NIL,
GWorldFlags(0));
FailOSErr(Erry);
SetGWorld (fDrawingPort, NIL);
IF ( NOT LockPixels(fDrawingPort^.portPixMap) ) THEN
FailOSErr(QDError);
EraseRect(FDrawingPort^.portRect);
UnlockPixels (fDrawingPort^.portPixMap);
SetGWorld (currPort, currDev);
CALLS YOU CAN'T DO WITHOUT
The new routines simplify the code and help prevent the typical errors you make
having to initialize all those special fields. It only makes sense to make your work